﻿using NBehave.Spec.NUnit;
using NUnit.Framework;
using System.Collections.Generic;
using System;
using Moq;
using Moq.Protected;

namespace MoqExamples
{
    [TestFixture]
    public class When_working_with_a_mock_of_an_interface
    {
        [Test]
        public void Mocks_mocks_and_all_other_variants_are_all_constructed_using_the_mock_class()
        {
            var mock = new Mock<ISampleClass>();
        }

        [Test]
        public void Moq_provides_a_terse_syntax_for_creating_mocks()
        {
            var mock = new Mock<ISampleClass>();
        }

        [Test]
        public void Calling_void_methods_will_do_nothing()
        {
            var mock = new Mock<ISampleClass>();

            mock.Object.VoidMethod();
        }

        [Test]
        public void Calling_methods_that_return_value_types_will_return_the_default_value_for_that_type()
        {
            var mock = new Mock<ISampleClass>();

            mock.Object.MethodThatReturnsInteger("foo").ShouldEqual(0);
        }

        [Test]
        public void Calling_methods_that_return_reference_types_will_return_null()
        {
            var mock = new Mock<ISampleClass>();

            mock.Object.MethodThatReturnsObject(1).ShouldBeNull();
        }

        [Test]
        public void You_can_tell_the_mock_what_value_to_return_when_is_method_is_called_with_specific_arguments()
        {
            var mock = new Mock<ISampleClass>();

            mock.Setup(x => x.MethodThatReturnsInteger("foo")).Returns(5);

            // calling the method with "foo" as the parameter will return 5
            mock.Object.MethodThatReturnsInteger("foo").ShouldEqual(5);

            // calling the method with anything other than "foo" as the 
            // parameter will return the default value
            mock.Object.MethodThatReturnsInteger("bar").ShouldEqual(0);
        }

        [Test]
        public void You_can_tell_the_mock_what_value_to_return_when_is_method_is_called_with_any_argument()
        {
            var mock = new Mock<ISampleClass>();

            mock.Setup(s => s.MethodThatReturnsInteger(It.IsAny<string>())).Returns(5);

            // now it doesn't matter what the parameter is, we'll always get 5
            mock.Object.MethodThatReturnsInteger("foo").ShouldEqual(5);
            mock.Object.MethodThatReturnsInteger("bar").ShouldEqual(5);
            mock.Object.MethodThatReturnsInteger(null).ShouldEqual(5);
        }

        [Test]
        public void You_can_get_fancy_with_parameters_in_mocks()
        {
            var mock = new Mock<ISampleClass>();

            mock.Setup(s => s.MethodThatReturnsInteger(It.Is<string>(x => x != null && x.Length > 2))).Returns(5);

            mock.Object.MethodThatReturnsInteger("fooo").ShouldEqual(5);
            mock.Object.MethodThatReturnsInteger("foo").ShouldEqual(5);
            mock.Object.MethodThatReturnsInteger("fo").ShouldEqual(0);
            mock.Object.MethodThatReturnsInteger("f").ShouldEqual(0);
            mock.Object.MethodThatReturnsInteger(null).ShouldEqual(0);
        }

        [Test]
        public void Handling_out_parameters_in_mocks()
        {
            var mock = new Mock<ISampleClass>();

            // Here's how you mock an "out" parameter.  
            var outInteger = 5;
            mock.Setup(s => s.MethodWithOutParameter(out outInteger));

            int i = 12345;
            mock.Object.MethodWithOutParameter(out i);
            i.ShouldEqual(5);
        }

        [Test]
        public void You_can_tell_the_mock_to_throw_an_exception_when_a_method_is_called()
        {
            var mock = new Mock<ISampleClass>();

            mock.Setup(s => s.MethodThatReturnsInteger("foo")).Throws(new InvalidOperationException());

            typeof(InvalidOperationException).ShouldBeThrownBy(
                () => mock.Object.MethodThatReturnsInteger("foo"));
        }

        [Test]
        public void You_can_check_to_see_if_a_method_was_called()
        {
            var mock = new Mock<ISampleClass>();

            mock.Object.MethodThatReturnsInteger("foo");

            mock.Verify(s => s.MethodThatReturnsInteger("foo"));
            mock.Verify(s => s.MethodThatReturnsInteger(It.IsAny<string>()));

        }

        [Test]
        public void You_can_check_to_see_if_a_method_was_called_a_certain_number_of_times()
        {
            var mock = new Mock<ISampleClass>();

            mock.Object.MethodThatReturnsInteger("foo");
            mock.Object.MethodThatReturnsInteger("bar");

            // this will pass
            mock.Verify(s => s.MethodThatReturnsInteger("foo"), Times.Once());

            // call the method a second time
            mock.Object.MethodThatReturnsInteger("foo");

            // now this will fail because we called it a second time
            typeof(MockException).ShouldBeThrownBy(
                () => mock.Verify(s => s.MethodThatReturnsInteger("foo"), Times.Once()));

            // some other options
            mock.Verify(s => s.MethodThatReturnsInteger("foo"), Times.Exactly(2));
            mock.Verify(s => s.MethodThatReturnsInteger("foo"), Times.AtLeastOnce());
            mock.Verify(s => s.MethodThatReturnsInteger("foo"), Times.AtMost(2));
        }

        [Test]
        public void Another_way_to_verify_expectations_instead_of_AssertWasCalled()
        {
            var mock = new Mock<ISampleClass>();

            // Here I'm setting up an expectation that a method will be called
            mock.Setup(s => s.MethodThatReturnsInteger("foo")).Returns(5).Verifiable();

            var output = mock.Object.MethodThatReturnsInteger("foo");
            output.ShouldEqual(5);

            // ... and now I'm verifying that the method was called
            mock.VerifyAll();
        }

        [Test]
        public void You_can_check_to_see_if_a_method_was_not_called()
        {
            var mock = new Mock<ISampleClass>();

            mock.Object.MethodThatReturnsInteger("foo");

            mock.Verify(s => s.MethodThatReturnsInteger("asdfdsf"), Times.Never());
            mock.Verify(s => s.MethodThatReturnsObject(It.IsAny<int>()), Times.Never());
            mock.Verify(s => s.VoidMethod(), Times.Never());
        }

        [Test]
        public void You_can_get_the_arguments_of_calls_to_a_method()
        {
            var mock = new Mock<ISampleClass>();

            //Not sure if this is the best way to do this
            var argument1 = string.Empty;
            var argument2 = string.Empty;

            mock.Setup(x => x.MethodThatReturnsInteger(It.IsAny<string>()))
                .Callback((string argument) => argument1 = argument);

            mock.Object.MethodThatReturnsInteger("foo");
            argument1.ShouldEqual("foo");

            mock.Setup(x => x.MethodThatReturnsInteger(It.IsAny<string>()))
                .Callback((string argument) => argument2 = argument);

            mock.Object.MethodThatReturnsInteger("bar");
            argument2.ShouldEqual("bar");
        }

        [Test]
        public void Properties_can_be_setup_on_mocks()
        {
            var mock = new Mock<ISampleClass>();

            mock.SetupProperty(x => x.Property, "baz");

            mock.Object.Property = "pie";
            mock.Object.Property.ShouldEqual("pie");

            //this works too
            mock.SetupProperty(x => x.Property, "foo");
            mock.Object.Property.ShouldEqual("foo");
        }

        [Test]
        public void You_can_use_verifyget_with_properties_on_a_mock()
        {
            var mock = new Mock<ISampleClass>();

            var result = mock.Object.Property;
            mock.VerifyGet(x => x.Property);
        }

        [Test]
        public void You_can_use_verifyset_with_properties_on_a_mock()
        {
            var mock = new Mock<ISampleClass>();

            mock.Object.Property = "pie";
            mock.VerifySet(x => x.Property);
        }

        [Test]
        public void You_can_tell_events_on_a_mock_to_fire()
        {
            var mock = new Mock<ISampleClass>();
            var eventWasHandled = false;

            // attach an event handler
            mock.Object.SomeEvent += (args, e) => eventWasHandled = true;

            // raise the event
            mock.Raise(s => s.SomeEvent += null, this, EventArgs.Empty);

            eventWasHandled.ShouldBeTrue();
        }

        [Test, Ignore("Don't know of an API available in Moq. Don't see a real world usage scenario for this either")]
        public void You_can_do_arbitrary_stuff_when_a_method_is_called()
        {
        }
    }

    [TestFixture]
    public class when_working_with_a_strict_mock
    {
        [Test]
        public void it_raises_exceptions_for_methods_that_do_not_have_an_expectation()
        {
            var mock = new Mock<ISampleClass>(MockBehavior.Strict);

            typeof(Exception).ShouldBeThrownBy(
                () => mock.Object.VoidMethod());
        }

        [Test]
        public void it_does_not_raise_exceptions_when_expectations_are_set_for_a_method()
        {
            var mock = new Mock<ISampleClass>(MockBehavior.Strict);

            mock.Setup(x => x.MethodThatReturnsInteger("foo")).Returns(0);
            mock.Object.MethodThatReturnsInteger("foo");
        }
    }

    [TestFixture]
    public class When_working_with_a_mock_of_a_concrete_class
    {
        [Test]
        public void Partial_mocks_are_still_just_mocks()
        {
            var mock = new Mock<SampleClass>();
        }

        [Test]
        public void You_can_pass_in_constuctor_arguments_and_moq_will_pick_the_constructor_that_fits()
        {
            var mock = new Mock<SampleClass>(new object[] { "foo" });

            mock.Object.SetByConstructor.ShouldEqual("foo");
        }

        [Test]
        public void Calling_non_virtual_methods_will_call_the_actual_method()
        {
            var mock = new Mock<SampleClass>();

            mock.Object.VoidMethod();
            mock.Object.VoidMethodWasCalled.ShouldBeTrue();
        }

        [Test]
        public void Calling_virtual_methods_will_call_the_actual_method()
        {
            var mock = new Mock<SampleClass>() { CallBase = true };

            mock.Object.VirtualMethod("foo").ShouldEqual(3);
            mock.Object.VirtualMethodWasCalled.ShouldBeTrue();

            mock.Verify(x => x.VirtualMethod("foo"));
        }

        [Test]
        public void You_can_mock_a_virtual_method_and_give_it_a_return_value()
        {
            var mock = new Mock<SampleClass>();

            mock.Setup(s => s.VirtualMethod("foo")).Returns(100);

            mock.Object.VirtualMethod("foo").ShouldEqual(100);

            // It's not actually going to run the real method since we mockbed it out.
            mock.Object.VirtualMethodWasCalled.ShouldBeFalse();

            mock.Verify(c => c.VirtualMethod("foo"));
        }

        [Test]
        public void You_can_have_virtual_methods_throw_an_exception_when_they_are_called()
        {
            var mock = new Mock<SampleClass>();

            mock.Setup(x => x.VirtualMethod("foo")).Throws(new InvalidOperationException());

            typeof(InvalidOperationException).ShouldBeThrownBy(
                () => mock.Object.VirtualMethod("foo"));
        }

        [Test]
        public void You_cannot_mock_a_non_virtual_method()
        {
            var mock = new Mock<SampleClass>();

            typeof(Exception).ShouldBeThrownBy(
                () => mock.Setup(s => s.NonVirtualMethod(1)).Returns(new List<int> { 3 }));
        }

        [Test, Ignore("Don't know of an API available in Moq. Don't see a real world usage scenario for this either")]
        public void You_can_use_WhenCalled_with_virtual_methods()
        {
        }

        [Test, Ignore("Don't know of an API available in Moq. Don't see a real world usage scenario for this either")]
        public void You_cannot_use_WhenCalled_with_non_virtual_methods()
        {
        }

        [Test]
        public void You_can_check_to_see_if_a_virtual_method_was_called()
        {
            var mock = new Mock<SampleClass>();

            mock.Object.VirtualMethod("foo");

            mock.Verify(s => s.VirtualMethod("foo"));
            mock.Verify(s => s.VirtualMethod(It.IsAny<string>()));
            mock.Verify(s => s.VirtualMethod("foo"), Times.Once());
        }

        [Test]
        public void You_cannot_verify_calls_on_a_non_virtual_method()
        {
            var mock = new Mock<SampleClass>();

            mock.Object.VoidMethod();

            typeof(Exception).ShouldBeThrownBy(
                () => mock.Verify(s => s.VoidMethod()));
        }

        [Test]
        public void You_can_get_the_arguments_of_calls_to_a_virtual_method()
        {
            var mock = new Mock<SampleClass>();

            var argumentPassed = string.Empty;

            mock.Setup(x => x.VirtualMethod("foo"))
                .Callback((string arg) => argumentPassed = arg);

            mock.Object.VirtualMethod("foo");

            argumentPassed.ShouldEqual("foo");
        }

        [Test]
        public void You_cannot_get_the_arguments_of_calls_to_a_non_virtual_method()
        {
            var mock = new Mock<SampleClass>();

            var argumentPassed = 0;

            typeof(Exception).ShouldBeThrownBy(
                () => mock.Setup(x => x.NonVirtualMethod(1))
                    .Callback((int arg) => argumentPassed = arg));

            mock.Object.NonVirtualMethod(1);
        }

        [Test]
        public void Non_virtual_properties_work_as_normal()
        {
            var mock = new Mock<SampleClass>();

            mock.Object.NonVirtualProperty = "foo";
            mock.Object.NonVirtualProperty.ShouldEqual("foo");
        }

        [Test]
        public void Virtual_properties_work_as_normal()
        {
            var mock = new Mock<SampleClass>() { CallBase = true };

            mock.Object.VirtualProperty = "foo";
            mock.Object.VirtualProperty.ShouldEqual("foo");
        }

        [Test]
        public void You_can_tell_virtual_events_on_a_partial_mock_to_fire()
        {
            var mock = new Mock<SampleClass>();

            var eventWasHandled = false;

            // attach an event handler
            mock.Object.SomeVirtualEvent += (args, e) => eventWasHandled = true;

            // raise the event
            mock.Raise(s => s.SomeVirtualEvent += null, this, EventArgs.Empty);

            eventWasHandled.ShouldBeTrue();
        }

        [Test]
        public void Non_virtual_events_work_normally()
        {
            var mock = new Mock<SampleClass>();

            var eventWasHandled = false;

            // attach an event handler
            mock.Object.NonVirtualEvent += (args, e) => eventWasHandled = true;

            // raise the event
            mock.Object.FireNonVirtualEvent();

            eventWasHandled.ShouldBeTrue();
        }
    }

    [TestFixture]
    public class when_testing_a_protected_method
    {
        [Test]
        public void use_a_slightly_different_syntax_to_test_these_methods()
        {
            //this is the same
            var mock = new Mock<SampleClass>();

            //setting up the mock to return a result is a bit different (requires 'using Moq.Protected;' namespace)
            mock.Protected().Setup<int>("SomeProtectedMethod").Returns(33);

            var result = mock.Object.CallSomeProtectedMethod();

            result.ShouldEqual(33);
        }

        [Test]
        public void you_can_also_test_with_different_input_parameters_to_the_mock()
        {
            var mock = new Mock<SampleClass>();

            mock.Protected().Setup<int>("ProtectedMethodWithParameter", "pie").Returns(11);
            mock.Protected().Setup<int>("ProtectedMethodWithParameter", "something else").Returns(99);

            var result = mock.Object.CallProtectedMethodWithParameter("pie");
            result.ShouldEqual(11);

            result = mock.Object.CallProtectedMethodWithParameter("something else");
            result.ShouldEqual(99);

            //returns default value for type
            result = mock.Object.CallProtectedMethodWithParameter("super kala fraga listic expe ala docious");
            result.ShouldEqual(0);
        }

        [Test]
        public void you_can_also_setup_returns_given_any_input_for_the_argument()
        {
            var mock = new Mock<SampleClass>();

            mock.Protected().Setup<int>("ProtectedMethodWithParameter", ItExpr.IsAny<string>()).Returns(1010101);

            var result = mock.Object.CallProtectedMethodWithParameter("pie");
            result.ShouldEqual(1010101);

            result = mock.Object.CallProtectedMethodWithParameter("something else");
            result.ShouldEqual(1010101);

            result = mock.Object.CallProtectedMethodWithParameter("super kala fraga listic expe ala docious");
            result.ShouldEqual(1010101);
        }
    }

    [TestFixture]
    public class when_needing_to_do_some_more_advanced_parameter_matching
    {
        [Test]
        public void you_can_use_this_API()
        {
            var mock = new Mock<ISampleClass>();

            mock.Setup(x => x.MethodThatReturnsInteger(ArgumentIsNotNullAndGreaterThan100OrEqualTo5())).Returns(77);

            //First example string length is exactly 5
            var result = mock.Object.MethodThatReturnsInteger("12345");
            result.ShouldEqual(77);

            //Second example string length is 101
            var longString = string.Empty;
            for (int i = 0; i < 101; i++)
            {
                longString += "!";
            }

            result = mock.Object.MethodThatReturnsInteger(longString);
            result.ShouldEqual(77);

            //Third example string length is 6, should return default integer value (0)
            result = mock.Object.MethodThatReturnsInteger("123456");
            result.ShouldEqual(0);
        }

        public string ArgumentIsNotNullAndGreaterThan100OrEqualTo5()
        {
            return Match<string>.Create(s => !String.IsNullOrEmpty(s) && (s.Length > 100 || s.Length == 5));
        }
    }

    public class when_testing_method_calls_on_the_mocked_object
    {
        [Test]
        public void i_can_assert_that_methods_were_called_when_calling_another_method_on_the_same_object()
        {
            var mock = new Mock<SampleClass>() { CallBase = true };

            mock.Object.VirtualMethodThatCallsAnotherVirtual();

            mock.Verify(x => x.AnotherVirtualMethod());
        }
    }
}